package projeto.lp2.grupo6.logica;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * Aluno:       
 *      Guilherme Monteiro 
 *      Italo Guedes 
 *      Tiago Leite
 * 
 * @author guilhermemg
 * @author tiagoln
 * @author italogas
 * 
 * Classe utilizada para gerar recomendacoes de estabelecimentos por maior afinidade.
 */
public class RecomendacaoPersonalizada extends Recomendacao {

    private List<Estabelecimento> listaRecomendacoes;
    private List<Usuario> listaDeExclusao;
    private Usuario usuarioAconselhado;

    /**
     * Construtor de recomendacao personalizada.
     * 
     * @param listaUsuarios - Lista com os usuarios do sistema.
     * @param usuarioAconselhado - Usuario para o qual a recomendacao sera gerada
     * @throws Exception
     */
    public RecomendacaoPersonalizada(List<Usuario> listaUsuarios, Usuario usuarioAconselhado) throws Exception {
        super(listaUsuarios);
        listaRecomendacoes = new ArrayList<Estabelecimento>();
        setUsuarioAconselhado(usuarioAconselhado);
    }

    /**
     * Retorna usuario a ser aconselhado.
     * 
     * @return usuario aconselhado
     */
    public Usuario getUsuarioAconselhado() {
        return usuarioAconselhado;
    }

    /**
     * Atribui um usuario a ser aconselhado.
     * 
     * @param usuarioAconselhado
     * @throws Exception 
     */
    public void setUsuarioAconselhado(Usuario usuarioAconselhado) throws Exception {
        if (usuarioAconselhado == null) {
            throw new Exception("Erro em usuario aconselhado nulo");
        }
        this.usuarioAconselhado = usuarioAconselhado;
    }

    /**
     * Este metodo retorna uma lista com a recomendacao para o usuario passado e com
     * quantidade de itens passados.
     * 
     * @param usuario - Usuario para o qual a recomendacao ira ser gerada.
     * @param quantidadeItens - Quantidade de intes a serem recomendados.
     * @return listaRecomendacoes - Lista com os estabelecimentos recomendados.
     * @param filtroTipo - Filtro de tipo de estabelecimento
     * @param palavraChave - Filtro de palavra chave
     * @throws Exception 
     */
    @Override
    public List<Estabelecimento> getRecomendacao(int quantidadeItens, List<TipoDeAlmocoServido> filtroTipo, String palavraChave) throws Exception {
        if (usuarioAconselhado == null) {
            throw new Exception("Erro em usuario nulo");
        }
        if (quantidadeItens < 0) {
            throw new Exception("Erro em quantidade de itens negativa");
        }
        if (quantidadeItens == 0) {
            throw new Exception("Erro em quantidade de itens nula");
        }
        if (filtroTipo == null) {
            throw new Exception("Erro em lista de tipos de almoco servidos nula");
        }
        if (filtroTipo.isEmpty()) {
            throw new Exception("Erro em lista de tipos de almoco servidos vazia");
        }
        if (palavraChave == null) {
            throw new Exception("Erro em palavra-chave nula");
        }
        if (palavraChave.isEmpty()) {
            throw new Exception("Erro em palavra-chave vazia");
        }

        listaDeExclusao = new ArrayList<Usuario>();
        listaDeExclusao.add(usuarioAconselhado);
        while (listaRecomendacoes.size() < quantidadeItens) {
            Usuario usuarioComMaisCompativel = retornaUsuarioMaisCompativel(getUsuarioAconselhado());
            if (usuarioComMaisCompativel == null) {
                return listaRecomendacoes;
            } else {
                List<Opiniao> listaOpinioesMaisCompativel = new ArrayList<Opiniao>(usuarioComMaisCompativel.getListaDeOpinioes());
                List<Opiniao> listaOpinioesUsuario = getUsuarioAconselhado().getListaDeOpinioes();
                ordenaListaOpinioes(listaOpinioesMaisCompativel);
                adicionaNaListaRecomendacoes(quantidadeItens,
                        listaOpinioesMaisCompativel, listaOpinioesUsuario, filtroTipo, palavraChave);
            }
        }
        return listaRecomendacoes;
    }

    /**
     * Ordena lista de opinioes da maior para a menor nota. 
     * 
     * @param listaOpinioes - Lista de opinioes ordenada.
     */
    private void ordenaListaOpinioes(List<Opiniao> listaOpinioes) {
        Collections.sort(listaOpinioes, new Comparator<Opiniao>() {

            @Override
            public int compare(Opiniao o1, Opiniao o2) {
                return o1.getNota() < o2.getNota() ? +1 : (o1.getNota() > o2.getNota() ? -1 : 0);
            }
        });
    }

    /**
     * Adiciona Estabelecimentos que serao sugeridos a lista de recomendacoes. 
     * O metodo so adiciona estabelecimentos que o usuario aconselhado
     * nao conhece e que foram bem avaliados pelo usuario mais compativel.
     * 
     * @param quantidadeItens - Quantidade limite de itens a ser atingida.
     * @param listaOpinioesMaisCompativel - Lista de opinioes do usuario com maior afinidade.
     * @param listaOpinioesUsuario - Lista de opinioes do usuario a ser aconselhado.
     * @param filtroTipo - Filtro de tipos de estabelecimento
     * @param palavraChave - Filtro de palavra chave
     */
    private void adicionaNaListaRecomendacoes(int quantidadeItens,
            List<Opiniao> listaOpinioesMaisCompativel,
            List<Opiniao> listaOpinioesUsuario, List<TipoDeAlmocoServido> filtroTipo, String palavraChave) {

        for (Opiniao opiniaoMaisCompativel : listaOpinioesMaisCompativel) {
            if (listaRecomendacoes.size() < quantidadeItens) {
                for (Opiniao opiniaoUsuario : listaOpinioesUsuario) {
                    if (opiniaoMaisCompativel.getEstabelecimento().getNome().equals(opiniaoUsuario.getEstabelecimento().getNome())
                            && opiniaoMaisCompativel.getNota() > 0 && opiniaoUsuario.getNota() == 0
                            && !listaRecomendacoes.contains(opiniaoMaisCompativel.getEstabelecimento())
                            && filtroTipo.contains(opiniaoMaisCompativel.getEstabelecimento().getTipoDeAlmoco())
                            && !opiniaoMaisCompativel.getEstabelecimento().getNome().toLowerCase().contains(palavraChave.toLowerCase())) {
                        listaRecomendacoes.add(opiniaoMaisCompativel.getEstabelecimento());
                    }
                }
            } else {
                break;
            }
        }
    }

    /**
     * Retorna usuario com maior compatibilidade ao usuario a ser aconselhado.
     * O metodo utiliza um produto escalar das notas dadas para avaliar a compatibilidade.
     * 
     * @param usuario - Usuario a ser aconselhado.
     * @return usuarioMaisCompativel - Usuario com maior afinidade ao usuario a ser aconselhado.
     */
    private Usuario retornaUsuarioMaisCompativel(Usuario usuario) throws Exception {
        int maiorPontuacao = 0;
        Usuario usuarioMaisCompativel = null;
        Iterator<Usuario> iteradorUsuarios = getListaUsuarios().iterator();
        Iterator<Opiniao> iteradorOpinioesUsuario = usuario.getListaDeOpinioes().iterator();
        Iterator<Opiniao> iteradorOpinioesUsuario2;
        while (iteradorUsuarios.hasNext()) {
            int somador = 0;
            Usuario usuarioDaLista = iteradorUsuarios.next();
            if (!listaDeExclusao.contains(usuarioDaLista)) {
                iteradorOpinioesUsuario2 = usuarioDaLista.getListaDeOpinioes().iterator();
                while (iteradorOpinioesUsuario.hasNext()) {
                    somador += (iteradorOpinioesUsuario.next().getNota() * iteradorOpinioesUsuario2.next().getNota());
                }
                if (somador > maiorPontuacao) {
                    maiorPontuacao = somador;
                    usuarioMaisCompativel = usuarioDaLista;
                } else if (somador == maiorPontuacao && usuarioMaisCompativel == null) {
                    usuarioMaisCompativel = usuarioDaLista;
                }
            }
        }
        listaDeExclusao.add(usuarioMaisCompativel);
        return usuarioMaisCompativel;
    }
}
